package com.sfx.shortLink.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sfx.shortLink.common.database.BaseDO;
import com.sfx.shortLink.common.utils.BeanUtils;
import com.sfx.shortLink.dao.entity.*;
import com.sfx.shortLink.dao.mapper.*;
import com.sfx.shortLink.dto.LinkAccessStatsDTO;
import com.sfx.shortLink.dto.LinkLocaleStatsDTO;
import com.sfx.shortLink.dto.req.ShortLinkGroupStatsAccessRecordReqDTO;
import com.sfx.shortLink.dto.req.ShortLinkGroupStatsReqDTO;
import com.sfx.shortLink.dto.req.ShortLinkStatsAccessRecordReqDTO;
import com.sfx.shortLink.dto.req.ShortLinkStatsReqDTO;
import com.sfx.shortLink.dto.resp.*;
import com.sfx.shortLink.service.ShortLinkStatsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;


@Service
@RequiredArgsConstructor
public class ShortLinkStatsServiceImpl implements ShortLinkStatsService {

    private final ILinkAccessStatsMapper linkAccessStatsMapper;
    private final ILinkAccessLogsMapper linkAccessLogsMapper;
    private final ILinkLocaleStatsMapper linkLocaleStatsMapper;
    private final ILinkBrowserMapper linkBrowserMapper;
    private final ILinkOsStatsMapper linkOsStatsMapper;
    private final ILinkDeviceStatsMapper linkDeviceStatsMapper;
    private final ILinkNetworkStatsMapper linkNetworkStatsMapper;


    @Override
    public ShortLinkStatsRespDTO oneShortLinkStats(ShortLinkStatsReqDTO requestParam) {
        // 1. 是否有用户访问
        List<LinkAccessStatsDTO> accessStatsDOList = linkAccessStatsMapper.listStatsByShortLink(requestParam);
        if (CollectionUtil.isEmpty(accessStatsDOList)) {
            // 为空说明没有用户进行访问,我们直接返回null, 前端直接展示 "还没有人访问"
            return null;
        }
        // 2. 统计基础访问详情(指定日期,统计好每一天的pv,uv,uip,情况)
        // 2.1 按照每天进行分割
        // 比如会被分成    uip
        // 2024-02-12
        // 2024-02-13
        // 2024-02-14
        List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();
        List<String> days = DateUtil.rangeToList(
                DateUtil.parse(requestParam.getStartDate()),
                DateUtil.parse(requestParam.getEndDate()),
                DateField.DAY_OF_MONTH).stream().map(DateUtil::formatDate).toList();

        days.forEach(day -> {
            accessStatsDOList.stream().filter(each -> Objects.equals(day, DateUtil.format(each.getDate(), "yyyy-MM-dd")))
                    .findFirst().ifPresentOrElse(item -> {
                // 有记录给他设置上
                ShortLinkStatsAccessDailyRespDTO dailyRespDTO = ShortLinkStatsAccessDailyRespDTO.builder()
                        .pv(item.getPv())
                        .uv(item.getUv())
                        .uip(item.getUip())
                        .date(day)
                        .build();
                daily.add(dailyRespDTO);
            }, () -> {
                // 没有有记录给他初始化上
                ShortLinkStatsAccessDailyRespDTO dailyRespDTO = ShortLinkStatsAccessDailyRespDTO.builder()
                        .pv(0)
                        .uv(0)
                        .uip(0)
                        .date(day)
                        .build();
                daily.add(dailyRespDTO);
            });
        });

        // 3. 统计指定日期内的中的 uv,pv,uip的量
        LinkAccessStatsDTO linkAccessStatsDO = linkAccessStatsMapper.statsByShortLink(requestParam);

        // 4. 统计区访问详情（仅国内）
        List<ShortLinkStatsLocaleCNRespDTO> localeCnStats = new ArrayList<>();
        List<LinkLocaleStatsDTO> localeStatsDOList = linkLocaleStatsMapper.listLocaleCNByShortLink(requestParam);
        /**
         * 黑龙江省,1
         * 山西省,1
         * 辽宁省,2
         */
        int localeSum = localeStatsDOList.stream().mapToInt(LinkLocaleStatsDTO::getCnt).sum();
        localeStatsDOList.forEach(each -> {
            double ratio = (double) each.getCnt() / localeSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsLocaleCNRespDTO build = ShortLinkStatsLocaleCNRespDTO.builder()
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .locale(each.getProvince())
                    .build();
            localeCnStats.add(build);
        });

        // 5. 小时访问详情
        List<Integer> hourStats = new ArrayList<>();
        List<LinkAccessStatsDTO> hourStatsMapList = linkAccessStatsMapper.listHourStatsByShortLink(requestParam);
        //   0   3
        //   1   2
        //   15  2
        for (int i = 0; i < 24; ++i) {
            AtomicInteger hour = new AtomicInteger(i);
            Integer pvCnt = hourStatsMapList.stream()
                    .filter(each -> Objects.equals(each.getHour(), hour.get()))
                    .findFirst().map(LinkAccessStatsDTO::getPv)
                    .orElse(0);
            hourStats.add(pvCnt);
        }

        // 6. 高频访问IP详情
        List<ShortLinkStatsTopIpRespDTO> topIpStats = new ArrayList<>();
        List<Map<String, Object>> topIpList = linkAccessLogsMapper.highFrequencyAccessIpByShortLink(requestParam);
        topIpList.forEach(each -> {
            ShortLinkStatsTopIpRespDTO build = ShortLinkStatsTopIpRespDTO.builder()
                    .ip(each.get("ip").toString())
                    .cnt(Integer.parseInt(each.get("cnt").toString()))
                    .build();
            topIpStats.add(build);
        });

        // 7. 一周访问详情
        List<Integer> weekdayStats = new ArrayList<>();
        List<LinkAccessStatsDTO> weekdayStatsList = linkAccessStatsMapper.listWeekdayStatsByShortLink(requestParam);
        for (int i = 0; i < 7; ++i) {
            AtomicInteger weekDay = new AtomicInteger(i);
            int cnt = weekdayStatsList.stream().filter(each -> Objects.equals(each.getWeekday(), weekDay.get()))
                    .findFirst()
                    .map(LinkAccessStatsDTO::getPv)
                    .orElse(0);
            weekdayStats.add(cnt);
        }

        // 8. 浏览器访问详情
        List<ShortLinkStatsBrowserRespDTO> browserStats = new ArrayList<>();
        List<LinkBrowserStatsDO> browserStatsList = linkBrowserMapper.listBrowserStatsByShortLink(requestParam);
        int browserSum = browserStatsList.stream().mapToInt(LinkBrowserStatsDO::getCnt).sum();
        browserStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / browserSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsBrowserRespDTO build = ShortLinkStatsBrowserRespDTO.builder()
                    .browser(each.getBrowser())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            browserStats.add(build);
        });

        // 9. 操作系统访问详情
        List<ShortLinkStatsOsRespDTO> osStats = new ArrayList<>();
        List<LinkOsStatsDO> osStatsList = linkOsStatsMapper.listOsStatsByShortLink(requestParam);
        int osSum = osStatsList.stream().mapToInt(LinkOsStatsDO::getCnt).sum();
        osStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / osSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsOsRespDTO build = ShortLinkStatsOsRespDTO.builder()
                    .os(each.getOs())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            osStats.add(build);
        });

        // 10. 访客访问类型详情 todo  这个sql有点问题
        List<ShortLinkStatsUvRespDTO> uvTypeStats = new ArrayList<>();
        List<Map<String, Object>> uvTypeList = linkAccessLogsMapper.listUvTypeByShortLink(requestParam);
        int newuserCnt = Integer.parseInt(uvTypeList.stream().findFirst()
                .map(each -> each.get("newUserCnt"))
                .map(Object::toString).orElse("0"));
        int oldUserCnt = Integer.parseInt(uvTypeList.stream().findFirst()
                .map(each -> each.get("oldUserCnt"))
                .map(Object::toString).orElse("0"));

        int sum = newuserCnt + oldUserCnt;
        double newRatio = (double) newuserCnt / sum;
        double newActualRatio = Math.round(newRatio * 100.0) / 100.0;
        ShortLinkStatsUvRespDTO newUser = ShortLinkStatsUvRespDTO.builder()
                .cnt(newuserCnt)
                .ratio(newActualRatio)
                .uvType("newUser").build();
        uvTypeStats.add(newUser);
        double oldRatio = (double) oldUserCnt / sum;
        double oldActualRatio = Math.round(oldRatio * 100.0) / 100.0;
        ShortLinkStatsUvRespDTO oldUser = ShortLinkStatsUvRespDTO.builder()
                .cnt(oldUserCnt)
                .ratio(oldActualRatio)
                .uvType("oldUser").build();
        uvTypeStats.add(oldUser);

        // 11. 访问设备类型详情
        List<ShortLinkStatsDeviceRespDTO> deviceStats = new ArrayList<>();
        List<LinkDeviceStatsDO> deviceStatsList = linkDeviceStatsMapper.listDeviceStatsByShortLink(requestParam);
        int deviceSum = osStatsList.stream().mapToInt(LinkOsStatsDO::getCnt).sum();
        deviceStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / deviceSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsDeviceRespDTO build = ShortLinkStatsDeviceRespDTO.builder()
                    .device(each.getDevice())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            deviceStats.add(build);
        });

        // 12. 访问网络类型详情
        List<ShortLinkStatsNetworkRespDTO> networkStats = new ArrayList<>();
        List<LinkNetworkStatsDO> networkStatsList = linkNetworkStatsMapper.listNetWorkStatsByShortLink(requestParam);
        int networkSum = networkStatsList.stream().mapToInt(LinkNetworkStatsDO::getCnt).sum();
        networkStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / networkSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsNetworkRespDTO build = ShortLinkStatsNetworkRespDTO.builder()
                    .cnt(each.getCnt())
                    .network(each.getNetwork())
                    .ratio(actualRatio)
                    .build();
            networkStats.add(build);
        });

        return ShortLinkStatsRespDTO.builder()
                .pv(linkAccessStatsDO.getPv())
                .uv(linkAccessStatsDO.getUv())
                .uip(linkAccessStatsDO.getUip())
                .daily(daily)
                .localeCnStats(localeCnStats)
                .hourStats(hourStats)
                .topIpStats(topIpStats)
                .weekdayStats(weekdayStats)
                .browserStats(browserStats)
                .osStats(osStats)
                .uvTypeStats(uvTypeStats)
                .deviceStats(deviceStats)
                .networkStats(networkStats)
                .build();
    }


    @Override
    public ShortLinkStatsRespDTO groupShortLinkStats(ShortLinkGroupStatsReqDTO requestParam) {
        // 1. 是否有用户访问
        List<LinkAccessStatsDTO> accessStatsDOList = linkAccessStatsMapper.listStatsByShortLinkGroup(requestParam);
        if (CollectionUtil.isEmpty(accessStatsDOList)) {
            // 为空说明没有用户进行访问,我们直接返回null, 前端直接展示 "还没有人访问"
            return null;
        }

        // 2. 统计基础访问详情(指定日期,统计好每一天的pv,uv,uip,情况)
        // 2.1 按照每天进行分割
        // 比如会被分成    uip
        // 2024-02-12
        // 2024-02-13
        // 2024-02-14
        List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();
        List<String> days = DateUtil.rangeToList(
                DateUtil.parse(requestParam.getStartDate()),
                DateUtil.parse(requestParam.getEndDate()),
                DateField.DAY_OF_MONTH).stream().map(DateUtil::formatDate).toList();

        days.forEach(day -> {
            accessStatsDOList.stream().filter(each -> Objects.equals(day, DateUtil.format(each.getDate(), "yyyy-MM-dd")))
                    .findFirst().ifPresentOrElse(item -> {
                // 有记录给他设置上
                ShortLinkStatsAccessDailyRespDTO dailyRespDTO = ShortLinkStatsAccessDailyRespDTO.builder()
                        .pv(item.getPv())
                        .uv(item.getUv())
                        .uip(item.getUip())
                        .date(day)
                        .build();
                daily.add(dailyRespDTO);
            }, () -> {
                // 没有有记录给他初始化上
                ShortLinkStatsAccessDailyRespDTO dailyRespDTO = ShortLinkStatsAccessDailyRespDTO.builder()
                        .pv(0)
                        .uv(0)
                        .uip(0)
                        .date(day)
                        .build();
                daily.add(dailyRespDTO);
            });
        });

        // 3. 统计指定日期内的中的 uv,pv,uip的量
        LinkAccessStatsDTO linkAccessStatsDO = linkAccessStatsMapper.statsByShortLinkGroup(requestParam);

        // 4. 统计区访问详情（仅国内）
        List<ShortLinkStatsLocaleCNRespDTO> localeCnStats = new ArrayList<>();
        List<LinkLocaleStatsDTO> localeStatsDOList = linkLocaleStatsMapper.listLocaleCNByShortLinkGroup(requestParam);
        /**
         * 黑龙江省,1
         * 山西省,1
         * 辽宁省,2
         */
        int localeSum = localeStatsDOList.stream().mapToInt(LinkLocaleStatsDTO::getCnt).sum();
        localeStatsDOList.forEach(each -> {
            double ratio = (double) each.getCnt() / localeSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsLocaleCNRespDTO build = ShortLinkStatsLocaleCNRespDTO.builder()
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .locale(each.getProvince())
                    .build();
            localeCnStats.add(build);
        });

        // 5. 小时访问详情
        List<Integer> hourStats = new ArrayList<>();
        List<LinkAccessStatsDTO> hourStatsMapList = linkAccessStatsMapper.listHourStatsByShortLinkGroup(requestParam);
        //   0   3
        //   1   2
        //   15  2
        for (int i = 0; i < 24; ++i) {
            AtomicInteger hour = new AtomicInteger(i);
            Integer pvCnt = hourStatsMapList.stream()
                    .filter(each -> Objects.equals(each.getHour(), hour.get()))
                    .findFirst().map(LinkAccessStatsDTO::getPv)
                    .orElse(0);
            hourStats.add(pvCnt);
        }

        // 6. 高频访问IP详情
        List<ShortLinkStatsTopIpRespDTO> topIpStats = new ArrayList<>();
        List<Map<String, Object>> topIpList = linkAccessLogsMapper.highFrequencyAccessIpByShortLinkGroup(requestParam);
        topIpList.forEach(each -> {
            ShortLinkStatsTopIpRespDTO build = ShortLinkStatsTopIpRespDTO.builder()
                    .ip(each.get("ip").toString())
                    .cnt(Integer.parseInt(each.get("cnt").toString()))
                    .build();
            topIpStats.add(build);
        });

        // 7. 一周访问详情
        List<Integer> weekdayStats = new ArrayList<>();
        List<LinkAccessStatsDTO> weekdayStatsList = linkAccessStatsMapper.listWeekdayStatsByShortLinkGroup(requestParam);
        for (int i = 0; i < 7; ++i) {
            AtomicInteger weekDay = new AtomicInteger(i);
            int cnt = weekdayStatsList.stream().filter(each -> Objects.equals(each.getWeekday(), weekDay.get()))
                    .findFirst()
                    .map(LinkAccessStatsDTO::getPv)
                    .orElse(0);
            weekdayStats.add(cnt);
        }

        // 8. 浏览器访问详情
        List<ShortLinkStatsBrowserRespDTO> browserStats = new ArrayList<>();
        List<LinkBrowserStatsDO> browserStatsList = linkBrowserMapper.listBrowserStatsByShortLinkGroup(requestParam);
        int browserSum = browserStatsList.stream().mapToInt(LinkBrowserStatsDO::getCnt).sum();
        browserStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / browserSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsBrowserRespDTO build = ShortLinkStatsBrowserRespDTO.builder()
                    .browser(each.getBrowser())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            browserStats.add(build);
        });

        // 9. 操作系统访问详情
        List<ShortLinkStatsOsRespDTO> osStats = new ArrayList<>();
        List<LinkOsStatsDO> osStatsList = linkOsStatsMapper.listOsStatsByShortLinkGroup(requestParam);
        int osSum = osStatsList.stream().mapToInt(LinkOsStatsDO::getCnt).sum();
        osStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / osSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsOsRespDTO build = ShortLinkStatsOsRespDTO.builder()
                    .os(each.getOs())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            osStats.add(build);
        });

        // 10. 访客访问类型详情  todo  这个sql有点问题
        List<ShortLinkStatsUvRespDTO> uvTypeStats = new ArrayList<>();
        List<Map<String, Object>> uvTypeList = linkAccessLogsMapper.listUvTypeByShortLinkGroup(requestParam);
        int newuserCnt = Integer.parseInt(uvTypeList.stream().findFirst()
                .map(each -> each.get("newUserCnt"))
                .map(Object::toString).orElse("0"));
        int oldUserCnt = Integer.parseInt(uvTypeList.stream().findFirst()
                .map(each -> each.get("oldUserCnt"))
                .map(Object::toString).orElse("0"));

        int sum = newuserCnt + oldUserCnt;
        double newRatio = (double) newuserCnt / sum;
        double newActualRatio = Math.round(newRatio * 100.0) / 100.0;
        ShortLinkStatsUvRespDTO newUser = ShortLinkStatsUvRespDTO.builder()
                .cnt(newuserCnt)
                .ratio(newActualRatio)
                .uvType("newUser").build();
        uvTypeStats.add(newUser);

        double oldRatio = (double) oldUserCnt / sum;
        double oldActualRatio = Math.round(oldRatio * 100.0) / 100.0;
        ShortLinkStatsUvRespDTO oldUser = ShortLinkStatsUvRespDTO.builder()
                .cnt(oldUserCnt)
                .ratio(oldActualRatio)
                .uvType("oldUser").build();
        uvTypeStats.add(oldUser);

        // 11. 访问设备类型详情
        List<ShortLinkStatsDeviceRespDTO> deviceStats = new ArrayList<>();
        List<LinkDeviceStatsDO> deviceStatsList = linkDeviceStatsMapper.listDeviceStatsByShortLinkGroup(requestParam);
        int deviceSum = osStatsList.stream().mapToInt(LinkOsStatsDO::getCnt).sum();
        deviceStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / deviceSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsDeviceRespDTO build = ShortLinkStatsDeviceRespDTO.builder()
                    .device(each.getDevice())
                    .ratio(actualRatio)
                    .cnt(each.getCnt())
                    .build();
            deviceStats.add(build);
        });

        // 12. 访问网络类型详情
        List<ShortLinkStatsNetworkRespDTO> networkStats = new ArrayList<>();
        List<LinkNetworkStatsDO> networkStatsList = linkNetworkStatsMapper.listNetWorkStatsByShortLinkGroup(requestParam);
        int networkSum = networkStatsList.stream().mapToInt(LinkNetworkStatsDO::getCnt).sum();
        networkStatsList.forEach(each -> {
            double ratio = (double) each.getCnt() / networkSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            ShortLinkStatsNetworkRespDTO build = ShortLinkStatsNetworkRespDTO.builder()
                    .cnt(each.getCnt())
                    .network(each.getNetwork())
                    .ratio(actualRatio)
                    .build();
            networkStats.add(build);
        });

        return ShortLinkStatsRespDTO.builder()
                .pv(linkAccessStatsDO.getPv())
                .uv(linkAccessStatsDO.getUv())
                .uip(linkAccessStatsDO.getUip())
                .daily(daily)
                .localeCnStats(localeCnStats)
                .hourStats(hourStats)
                .topIpStats(topIpStats)
                .weekdayStats(weekdayStats)
                .browserStats(browserStats)
                .osStats(osStats)
                .uvTypeStats(uvTypeStats)
                .deviceStats(deviceStats)
                .networkStats(networkStats)
                .build();
    }

    @Override
    public IPage<ShortLinkStatsAccessRecordRespDTO> shortLinkStatsAccessRecord(ShortLinkStatsAccessRecordReqDTO requestParam) {
        LambdaQueryWrapper<LinkAccessLogsDO> linkAccessLogsQueryWrapper = Wrappers.lambdaQuery(LinkAccessLogsDO.class)
                .eq(LinkAccessLogsDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(LinkAccessLogsDO::getGid, requestParam.getGid())
                .between(BaseDO::getCreateTime, requestParam.getStartDate(), requestParam.getEndDate());
        IPage<LinkAccessLogsDO> linkAccessLogsPage = linkAccessLogsMapper.selectPage(requestParam, linkAccessLogsQueryWrapper);
        if (linkAccessLogsPage.getRecords().isEmpty()) {
            return null;
        }

        IPage<ShortLinkStatsAccessRecordRespDTO> recordPage =
                linkAccessLogsPage.convert(each -> BeanUtils.convert(each, ShortLinkStatsAccessRecordRespDTO.class));

        List<String> users = recordPage.getRecords().stream()
                .map(ShortLinkStatsAccessRecordRespDTO::getUser)
                .toList();

        // user  type
        // user  type
        List<Map<String, Object>> uvTypeByUsers = linkAccessLogsMapper.selectUvTypeByUsers(requestParam.getGid(),
                requestParam.getFullShortUrl(), requestParam.getStartDate(), requestParam.getEndDate()
                , users);
        recordPage.getRecords().forEach(each -> {
            String uvType = uvTypeByUsers.stream().filter(item -> item.get("user").equals(each.getUser()))
                    .findFirst()
                    .map(item -> item.get("uvType"))
                    .map(Object::toString)
                    .orElse("旧访客");
            each.setUvType(uvType);
        });
        return recordPage;
    }


    @Override
    public IPage<ShortLinkStatsAccessRecordRespDTO> groupShortLinkStatsAccessRecord(ShortLinkGroupStatsAccessRecordReqDTO requestParam) {

        LambdaQueryWrapper<LinkAccessLogsDO> linkAccessLogsQueryWrapper = Wrappers.lambdaQuery(LinkAccessLogsDO.class)
                .eq(LinkAccessLogsDO::getGid, requestParam.getGid())
                .between(BaseDO::getCreateTime, requestParam.getStartDate(), requestParam.getEndDate());
        IPage<LinkAccessLogsDO> linkAccessLogsPage = linkAccessLogsMapper.selectPage(requestParam, linkAccessLogsQueryWrapper);

        if (linkAccessLogsPage.getRecords().isEmpty()) {
            return null;
        }
        IPage<ShortLinkStatsAccessRecordRespDTO> recordPage =
                linkAccessLogsPage.convert(each -> BeanUtils.convert(each, ShortLinkStatsAccessRecordRespDTO.class));

        List<String> users = recordPage.getRecords().stream()
                .map(ShortLinkStatsAccessRecordRespDTO::getUser)
                .toList();

        // user  type
        // user  type
        List<Map<String, Object>> uvTypeByUsers = linkAccessLogsMapper.selectUvTypeByUsersGroup(requestParam.getGid(),
                requestParam.getStartDate(), requestParam.getEndDate()
                , users);
        recordPage.getRecords().forEach(each -> {
            String uvType = uvTypeByUsers.stream().filter(item -> item.get("user").equals(each.getUser()))
                    .findFirst()
                    .map(item -> item.get("uvType"))
                    .map(Object::toString)
                    .orElse("旧访客");
            each.setUvType(uvType);
        });
        return recordPage;
    }
}